//! Headers which are parts of application.
#include "HDRLightsDemo.hpp"
#include "HDRDemoConstants.hpp"
#include "SceneManager.hpp"
#include "LookAtMatrix.hpp"
#include "ProjectionMatrix.hpp"
#include "MessageDispatcher.hpp"
#include "InputManager.hpp"
#include "Extensions/ExtensionsLoader.hpp"
#include "ScreenSystem/DemoSceneScreen.hpp"
#include "ScreenSystem/SpaceGameScreen.hpp"
#include "ScreenSystem/ScreenManager.hpp"
#include "Mouse/MouseManager.hpp"
#include "ResourceManagers/TextureManager.hpp"
#include "Mathematics/MMatrix4.hpp"
#include "Mathematics/MVector3D.hpp"
#include "Particles/EmitterManager.hpp"
//! Headers for external functionality ( text files loading for shaders /
//! FreeImage library providing loading of graphic files like .PNG, .BMP, etc. ).
#include "ResourceManagers/textfile.h"
#include "ResourceManagers/FreeImage.h"
#include "ResourceManagers/TextureObject.hpp"

//! Headers for asserts and time and math functions.
#include <time.h>
#include <math.h>
#include <assert.h>

//! Headers concerning OpenGL.
//#include <GL/glut.h>
//#include <GL/glext.h>
#include "Globals.hpp"

//! Be aware that LIGHTS_IN_TEX and MULTIPLE_LIGHTS_ON_REGISTERS should not be used together.
//#define _NO_SHADERS
//#define MULTIPLE_LIGHTS_ON_REGISTERS
//#define LIGHTS_IN_TEXTURE
#define PHONG_SHADING


HDRLightsDemo::HDRLightsDemo(int argc, char **argv):
m_angle( 0.0f ),
m_x( 0.0f ),
m_y( 15.0f ),
m_z( 20.0f )
{
	m_argc = argc;
	m_argv = argv;

	srand( (unsigned)time( NULL ) );

	InitGLUT();
	InitSpaceGame();
}

HDRLightsDemo::~HDRLightsDemo()
{
	MessageDispatcher::destroyInstance();
	SceneManager::GetInstance()->UnregisterAllEntities();
	ExtensionsLoader::destroyInstance();
	InputManager::destroyInstance();

	delete m_spaceGame;
}

bool HDRLightsDemo::InitSpaceGame()
{
	//! Create ExtensionsLoader.
	ExtensionsLoader::createInstance();

	//! Set light parameters.
	GLfloat LightAmbient[] = { 0.2f, 0.2f, 0.2f, 1.0f },
			LightDiffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f },
			LightSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f },
			LightPosition[] = { 0.0f, 10.0f, 5.0f };

	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient );
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse );
	glLightfv(GL_LIGHT0, GL_SPECULAR, LightSpecular );
	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition );

	//! Initialise FreeImage library with using only local plugins.
	FreeImage_Initialise( true );

	//! Create managers.
	SceneManager::GetInstance();
	CameraManager::GetInstance();
	ScreenManager::GetInstance();
	MouseManager::GetInstance();
	TextureManager::GetInstance();
	EmitterManager::GetInstance();
	MessageDispatcher::createInstance();

	//InitDepthTexture();
	//! Create screen.
	m_spaceGame = new SpaceGameScreen();;

#ifndef _NO_SHADERS
	LoadShaders();
#endif

	//! CAMERA MATRICES
	ProjectionMatrix* projMat = new ProjectionMatrix( 45.0f, (float)800/600, 1.0f, 50.0f );
	MMatrix4 projection = projMat->GetProjectionMatrix();

	Globals::GetInstance()->SetCameraMatrix( PROJECTION_MATRIX, projection );
	delete projMat; projMat = NULL;

	CameraObject* camera = CameraManager::GetInstance()->GetActiveCamera();
	MVector3D pos = camera->GetPosition();
	MVector3D tar = camera->GetTarget();
	MVector3D up  = camera->GetUpVec();

	LookAtMatrix* lookAt = new LookAtMatrix( pos, tar, up );
	MMatrix4 modelViewMatrix = lookAt->GetModelViewMatrix();
	Globals::GetInstance()->SetCameraMatrix( MODELVIEW_MATRIX, modelViewMatrix );
	camera = NULL;
	delete lookAt; lookAt = NULL;



	//! LIGHT MATRICES
	projMat = new ProjectionMatrix( 45.0f, 1.0f, 0.1f, 25.0f );
	projection = projMat->GetProjectionMatrix();

	Globals::GetInstance()->SetLightMatrix( PROJECTION_MATRIX, projection );
	delete projMat; projMat = NULL;




	glMatrixMode( GL_PROJECTION_MATRIX );
	glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( PROJECTION_MATRIX ) );

	glMatrixMode( GL_MODELVIEW_MATRIX );
	glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( MODELVIEW_MATRIX ) );

	return true;
}

bool HDRLightsDemo::InitDemo()
{
	//! Create ExtensionsLoader.
	ExtensionsLoader::getInstance();

	//! Set light parameters.
	GLfloat LightAmbient[] = { 0.2f, 0.2f, 0.2f, 1.0f },
			LightDiffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f },
			LightSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f },
			LightPosition[] = { 3.5f, 12.0f, 13.0f };

	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient );
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse );
	glLightfv(GL_LIGHT0, GL_SPECULAR, LightSpecular );
	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition );

	//! Initialise FreeImage library with using only local plugins.
	FreeImage_Initialise( true );

	//! Create managers.
	SceneManager::GetInstance();
	CameraManager::GetInstance();
	ScreenManager::GetInstance();
	MouseManager::GetInstance();
	TextureManager::GetInstance();

	InitDepthTexture();
	//! Create screen.
	//m_demoScene = new DemoSceneScreen;

#ifndef _NO_SHADERS
	LoadShaders();
#endif

//! preps for shadow mapping
	//cout << "GL Proj matrix: " << endl;
	//printf( " %.2f  %.2f  %.2f  %.2f \n %.2f  %.2f  %.2f  %.2f \n %.2f  %.2f  %.2f  %.2f \n %.2f  %.2f  %.2f  %.2f \n", proj[0], proj[1], proj[2], proj[3],
	//	proj[4], proj[5], proj[6], proj[7], proj[8], proj[9], proj[10], proj[11], proj[12], proj[13], proj[14], proj[15] );

    //! CAMERA MATRICES
	ProjectionMatrix* projMat = new ProjectionMatrix( 45.0f, (float)800/600, 0.1f, 50.0f );
	MMatrix4 projection = projMat->GetProjectionMatrix();

	Globals::GetInstance()->SetCameraMatrix( PROJECTION_MATRIX, projection );
	delete projMat; projMat = NULL;

	CameraObject* camera = CameraManager::GetInstance()->GetActiveCamera();
	MVector3D pos = camera->GetPosition();
	MVector3D tar = camera->GetTarget();
	MVector3D up  = camera->GetUpVec();

	LookAtMatrix* lookAt = new LookAtMatrix( pos, tar, up );
	MMatrix4 modelViewMatrix = lookAt->GetModelViewMatrix();
	Globals::GetInstance()->SetCameraMatrix( MODELVIEW_MATRIX, modelViewMatrix );
	camera = NULL;
	delete lookAt; lookAt = NULL;



	//! LIGHT MATRICES
	projMat = new ProjectionMatrix( 45.0f, 1.0f, 0.1f, 25.0f );
	projection = projMat->GetProjectionMatrix();

	Globals::GetInstance()->SetLightMatrix( PROJECTION_MATRIX, projection );
	delete projMat; projMat = NULL;




	glMatrixMode( GL_PROJECTION_MATRIX );
	glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( PROJECTION_MATRIX ) );

	//glMatrixMode( GL_MODELVIEW_MATRIX );
	//glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( MODELVIEW_MATRIX ) );

	return true;
}

void HDRLightsDemo::InitGLUT()
{
	//! Create input manager in order to register it's callback functions.
	InputManager::createInstance();

	//! Initialises GLUT, enables some OpenGL states and sets callback functions.
	glutInit( &m_argc, m_argv );
	glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA );

	glutInitWindowPosition( WINDOW_X_POS, WINDOW_Y_POS );
	glutInitWindowSize( WINDOW_WIDTH , WINDOW_HEIGHT );
	glutCreateWindow( WINDOW_CAPTION );

	glutMouseFunc( &MouseManager::GetInstance()->ProcessMouseClicks );
	glutMotionFunc( &MouseManager::GetInstance()->ProcessMouseMotion );

	//! receive keyboard events
	glutKeyboardFunc(InputManager::receiveKeyboardPress);
	glutKeyboardUpFunc(InputManager::receiveKeyboardRelease);
	glutSpecialFunc(InputManager::receiveKeyboardSpecialPress);
	glutSpecialUpFunc(InputManager::receiveKeyboardSpecialRelease);

	glEnable( GL_DEPTH_TEST );

	//glutDisplayFunc( &SceneManager::GetInstance()->UpdateScene );
	//glutIdleFunc( &SceneManager::GetInstance()->UpdateScene );
	glutDisplayFunc( &UpdateDemo );
	glutIdleFunc( &UpdateDemo );

	glutReshapeFunc( &HDRLightsDemo::ResizeWindow );

	//glutDisplayFunc( &SceneManager::GetInstance()->UpdateScene );
	glutDisplayFunc( &UpdateDemo );

#ifdef _NO_SHADERS
	glEnable( GL_LIGHTING );
	glEnable( GL_LIGHT0 );
	glShadeModel( GL_SMOOTH );
#endif

	GLfloat LightPosition[3];
	LightPosition[0] = 3.5;
	LightPosition[1] = 10.0f;
	LightPosition[2] = 13.0f;

	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition );

}

void HDRLightsDemo::GoSleep( clock_t wait )
{
	//! Puts application into sleep for specified time.
	clock_t goal;
	goal = wait + clock();
	while( goal > clock() )
	;
}
void HDRLightsDemo::LoadShaders()
{
	ProjectionMatrix* projection = new ProjectionMatrix( 45.0f, 1.0f, 0.1f, 25.0f );

	LoadShader( GAME_LIGHTING );
	LoadShader( GAME_DMAP_GENERATOR );

	InitDepthTexture();
	//assert( SetProjMatrixForShader(ExtensionsLoader::getInstance()->GetShader( GAME_LIGHTING ), "lproj", projection));
	//SetTextureForShader( ExtensionsLoader::GetInstance()->GetShader( DEPTH_MAP_GENERATOR ), "depthMap" );
	//assert( SetTextureForShader( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER ), "shadowMap", 1 ) );
	assert( SetTextureForShader(ExtensionsLoader::getInstance()->GetShader( GAME_LIGHTING ), "depthMap", *Globals::GetInstance()->GetSpecialTexture(SHADOW_MAP)));
	//assert( SetTextureForShader( ExtensionsLoader::GetInstance()->GetShader( DEPTH_MAP_GENERATOR ), "lightDirs", 0 ) );

	assert( SetProjMatrixForShader(ExtensionsLoader::getInstance()->GetShader( GAME_DMAP_GENERATOR ), "lightProjection", projection));
	//assert( SetProjMatrixForShader( ExtensionsLoader::GetInstance()->GetShader( LIGHTING_SHADER ), "lightProjection", projection ) );
//	SetProjMatrixForShader( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ), "lproj", projection );

	GenerateLights();
/////////////////////////////////////
//		GET LOC FOR MV_INV_MATRIX
/////////////////////////////////////
	ExtensionsLoader::glUseProgramObjectARB(ExtensionsLoader::getInstance()->GetShader(GAME_LIGHTING));

	//*(ExtensionsLoader::getInstance()->GetMVInvLoc()) = ExtensionsLoader::glGetUniformLocationARB(ExtensionsLoader::getInstance()->GetShader(GAME_LIGHTING), "camIMV" );

	//assert( *(ExtensionsLoader::getInstance()->GetMVInvLoc()) >= 0 );

	//MMatrix4* invMatrix = new MMatrix4;
	//GLfloat* mvMat = (GLfloat*)Globals::GetInstance()->GetCameraMatrix(MODELVIEW_MATRIX);
	//invMatrix->InverseMatrix(MMatrix4( mvMat[0], mvMat[1], mvMat[2], mvMat[3], mvMat[4],
	//	mvMat[5], mvMat[6], mvMat[7], mvMat[8], mvMat[9], mvMat[10], mvMat[11],
	//	mvMat[12], mvMat[13], mvMat[14], mvMat[15]) );

	//ExtensionsLoader::glUniformMatrix4fvARB( *(ExtensionsLoader::getInstance()->GetMVInvLoc()), 1, GL_FALSE, invMatrix->m_matrix );	

/////////////////////////////////////
	//ExtensionsLoader::glUseProgramObjectARB( ExtensionsLoader::getInstance()->GetShader( LIGHTING_SHADER ) );
}

void HDRLightsDemo::CreateScene()
{

}

void HDRLightsDemo::PrintInfoLog( GLhandleARB obj )
{
	//! Prints log about shader's initialisation.

    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	ExtensionsLoader::glGetObjectParameterivARB( obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infologLength );

    if (infologLength > 0)
    {
        infoLog = ( char * )malloc( infologLength );
		ExtensionsLoader::glGetInfoLogARB( obj, infologLength, &charsWritten, infoLog );
		printf( "%s\n",infoLog );
        free( infoLog );
    }
}

void HDRLightsDemo::UpdateDemo()
{
	clock_t tick = clock();

	MessageDispatcher::getInstance()->update(tick);
	SceneManager::GetInstance()->UpdateScene(tick);
	CameraManager::GetInstance()->Update(tick);
}

void HDRLightsDemo::InitDepthTexture()
{
	float* shadowTexture;

	shadowTexture = new float[ 1024*1024*3 ];

	//! Set depth to maximum.
	for( int i = 0; i < 1024*1024*3; ++i )
	{
		shadowTexture[i] = 0.0f;
	}

	glGenTextures( 1, Globals::GetInstance()->GetSpecialTexture( SHADOW_MAP ) );
	
	glBindTexture( GL_TEXTURE_2D, *(Globals::GetInstance()->GetSpecialTexture( SHADOW_MAP )) );
	//glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 16, 8, 0, GL_DEPTH_COMPONENT, GL_FLOAT, shadowTexture );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16, 1024, 1024, 0, GL_RGB, GL_FLOAT, shadowTexture );
	
	//TextureManager::GetInstance()->LoadTexture( WALL_TEST_TEX );
	//TextureObject* tex = (TextureObject*)TextureManager::GetInstance()->GetTexture( WALL_TEST_TEX );
	
	//glTexImage2D( GL_TEXTURE_2D, 1, GL_RGB, 16,8, 0, GL_RGB, GL_UBYTE, tex->GetTextureID() );
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
}

void HDRLightsDemo::GenerateLights()
{
	srand( (unsigned)time( NULL ) );

	float* lpos;
	GLint loc1;

	lpos = new float[3];
	lpos[0] = 0.0f; lpos[1] = 5.0f; lpos[2] = 13.0f;

	loc1 = ExtensionsLoader::glGetUniformLocationARB( ExtensionsLoader::getInstance()->GetShader( LIGHTING_SHADER),"lpos" );
	ExtensionsLoader::glUniform3fvARB( loc1, 1,lpos );

	LookAtMatrix* lookAt;
	MVector3D tar = MVector3D( 0.0f, 0.0f, 0.0f );
	MVector3D up = MVector3D( 0.0f, 1.0f, 0.0f );
	MVector3D pos = MVector3D(lpos[0], lpos[1], lpos[2]);

	lookAt = new LookAtMatrix( pos, tar, up );
	MMatrix4 modelViewMatrix = lookAt->GetModelViewMatrix();
	Globals::GetInstance()->SetLightMatrix( MODELVIEW_MATRIX, modelViewMatrix );

	delete lookAt; lookAt = NULL;
	delete[] lpos;
}

void HDRLightsDemo::LoadShader( enum Shader whichShader )
{
	char *vs = NULL, *fs = NULL;
	int maxFragmentALUOps;

	m_v  = ExtensionsLoader::glCreateShaderObjectARB( GL_VERTEX_SHADER_ARB );
	m_f  = ExtensionsLoader::glCreateShaderObjectARB( GL_FRAGMENT_SHADER_ARB);

	//sprawdzenie ilosci operacji ALU wykonywanych prze karte w jednym programie
	ExtensionsLoader::glGetProgramivARB( GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB, &maxFragmentALUOps );
	printf("GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB: %d\n", maxFragmentALUOps );

	//! Read appropriate shader file.
	switch( whichShader )
	{
	case GAME_DMAP_GENERATOR:
		vs = textFileRead( GAME_DMAP_GEN_VERTEX_PROGRAM_SRC );
		fs = textFileRead( GAME_DMAP_GEN_FRAGMENT_PROGRAM_SRC );
		break;

	case GAME_LIGHTING:
		vs = textFileRead( GAME_LIGHTING_VERTEX_PROGRAM_SRC );
		fs = textFileRead( GAME_LIGHTING_FRAGMENT_PROGRAM_SRC );
		break;

	case LIGHTING_SHADER:
		vs  = textFileRead( LIGHTING_VERTEX_PROGRAM_SOURCE );
		fs  = textFileRead( LIGHTING_FRAGMENT_PROGRAM_SOURCE );
		break;

	case DEPTH_MAP_RENDERER:
		vs  = textFileRead( DEPTH_MAP_REND_VERTEX_PROGRAM_SRC );
		fs  = textFileRead( DEPTH_MAP_REND_FRAGMENT_PROGRAM_SRC );
		break;

	case DEPTH_MAP_GENERATOR:
		vs  = textFileRead( DEPTH_MAP_GEN_VERTEX_PROGRAM_SRC );
		fs  = textFileRead( DEPTH_MAP_GEN_FRAGMENT_PROGRAM_SRC );
		break;

	case SHADOW_TEST:
		vs = textFileRead( SHADOW_TEST_VERTEX_PROGRAM_SRC );
		fs = textFileRead( SHADOW_TEST_FRAGMENT_PROGRAM_SRC );

	case PHONG_SHADER:
		vs = textFileRead( PHONG_SHADING_VERTEX_PROGRAM_SRC );
		fs = textFileRead( PHONG_SHADING_FRAGMENT_PROGRAM_SRC );
	}

	const char * vv  = vs;
	const char * ff  = fs;

	ExtensionsLoader::glShaderSourceARB( m_v,  1, &vv, NULL );
	ExtensionsLoader::glShaderSourceARB( m_f,  1, &ff, NULL );

	free( vs ); free( fs );

	//kompilacja shaderow
	ExtensionsLoader::glCompileShaderARB( m_v );
	ExtensionsLoader::glCompileShaderARB( m_f );

	PrintInfoLog( m_v );
	PrintInfoLog( m_f );

	m_shader = ExtensionsLoader::glCreateProgramObjectARB();
	ExtensionsLoader::glAttachObjectARB( m_shader, m_v );
	ExtensionsLoader::glAttachObjectARB( m_shader, m_f );

	ExtensionsLoader::glLinkProgramARB( m_shader );
	PrintInfoLog( m_shader );

	switch( whichShader )
	{
	case GAME_DMAP_GENERATOR:
		ExtensionsLoader::getInstance()->SetShader( m_shader, GAME_DMAP_GENERATOR );
		break;

	case GAME_LIGHTING:
		ExtensionsLoader::getInstance()->SetShader( m_shader, GAME_LIGHTING );
		break;

	case LIGHTING_SHADER:
		ExtensionsLoader::getInstance()->SetShader( m_shader, LIGHTING_SHADER );
		break;

	case DEPTH_MAP_RENDERER:
		ExtensionsLoader::getInstance()->SetShader( m_shader, DEPTH_MAP_RENDERER );
		break;

	case DEPTH_MAP_GENERATOR:
		ExtensionsLoader::getInstance()->SetShader( m_shader, DEPTH_MAP_GENERATOR );
		break;

	case SHADOW_TEST:
		ExtensionsLoader::getInstance()->SetShader( m_shader, SHADOW_TEST );
		break;

	case PHONG_SHADER:
		ExtensionsLoader::getInstance()->SetShader( m_shader, PHONG_SHADER );
	}

	ExtensionsLoader::glUseProgramObjectARB(m_shader);
}

bool HDRLightsDemo::SetTextureForShader(GLhandleARB shader, std::string texName, int texturingUnit )
{
	ExtensionsLoader::glUseProgramObjectARB( shader );

	GLint texLoc;

	texLoc = ExtensionsLoader::glGetUniformLocationARB( shader, texName.c_str() );

	if( texLoc < 0 )
		return false;

	ExtensionsLoader::glUniform1iARB( texLoc, texturingUnit );

	return true;
}

bool HDRLightsDemo::SetProjMatrixForShader(GLhandleARB shader, std::string matrixName, ProjectionMatrix* projMat)
{
	ExtensionsLoader::glUseProgramObjectARB( shader );

	GLint matrixLoc;
	GLfloat *proj = new GLfloat[16];


	MMatrix4 mat = projMat->GetProjectionMatrix();

	proj[0] = mat.m_matrix[0]; proj[1] = mat.m_matrix[1]; proj[2] = mat.m_matrix[2]; proj[3] = mat.m_matrix[3];
	proj[4] = mat.m_matrix[4]; proj[5] = mat.m_matrix[5]; proj[6] = mat.m_matrix[6]; proj[7] = mat.m_matrix[7];
	proj[8] = mat.m_matrix[8]; proj[9] = mat.m_matrix[9]; proj[10] = mat.m_matrix[10]; proj[11] = mat.m_matrix[11];
	proj[12] = mat.m_matrix[12]; proj[13] = mat.m_matrix[13]; proj[14] = mat.m_matrix[14]; proj[15] = mat.m_matrix[15];

	matrixLoc = ExtensionsLoader::glGetUniformLocationARB( shader, matrixName.c_str() );

	if( matrixLoc < 0 )
		return false;

	ExtensionsLoader::glUniformMatrix4fvARB( matrixLoc, 1, GL_FALSE, proj );

	return true;
}

//EOF